package com.zhang.common.core.utils.file;

import com.zhang.common.core.constant.Constants;
import com.zhang.common.core.domain.config.UploadImageConfig;
import com.zhang.common.core.domain.file.MockMultipartFile;
import com.zhang.common.core.exception.ServiceException;
import com.zhang.common.core.exception.file.FileInvalidExtensionException;
import com.zhang.common.core.exception.file.FileNameLengthLimitExceededException;
import com.zhang.common.core.exception.file.FileSizeLimitExceededException;
import com.zhang.common.core.utils.DateUtils;
import com.zhang.common.core.utils.StringUtils;
import com.zhang.common.core.utils.uuid.SeqUtils;
import net.coobird.thumbnailator.Thumbnails;
import net.coobird.thumbnailator.geometry.Positions;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @Class com.zhang.common.core.utils.file
 * @Author zhang
 * @Date 2025/9/11 17:19
 * @Description 文件上传工具类
 **/
public class FileUploadUtils {

    /**
     * 日志打印
     */
    private static final Logger log = LoggerFactory.getLogger(FileUploadUtils.class);

    /**
     * 默认大小 50M
     */
    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;

    /**
     * 默认的文件名最大长度 100
     */
    public static final int DEFAULT_FILE_NAME_LENGTH = 100;

    /***
     * 一行的总宽度（像素）
     **/
    private static int maxWidth = 1000;

    /***
     * 宽度超过3000的文件为大文件 采用fontMAX
     **/
    private static final int BIGFILE = 3000;

    /***
     * 宽度超过2000 小于3000的文件为大文件 fontMedium
     **/
    private static final int MEDFILE = 2000;


    /***
     * 宽度超过1000 小于2000的文件为大文件 fontSmall
     **/
    private static final int SMALLFILE = 1000;

    /**
     * 水印文字字体
     **/
    private static final Font FONTMAX = new Font("宋体", Font.BOLD, 80);
    private static final Font FONTBIG = new Font("宋体", Font.BOLD, 60);
    private static final Font FONTMEDIUM = new Font("宋体", Font.BOLD, 40);
    private static final Font FONTSMALL = new Font("宋体", Font.BOLD, 20);

    /**
     * 根据文件路径上传
     *
     * @param baseDir 相对应用的基目录
     * @param file    上传的文件
     * @return 文件名称
     */
    public static String upload(String baseDir, MultipartFile file) throws IOException {
        try {
            return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
        } catch (IOException | FileInvalidExtensionException e) {
            log.error("文件上传异常: {}", e.getMessage(), e);
            throw new IOException(e.getMessage(), e);
        }
    }

    /**
     * 文件上传
     *
     * @param baseDir 相对应用的基目录
     * @param file 上传的文件
     * @param allowedExtension 上传文件类型
     * @return 返回上传成功的文件名
     * @throws FileSizeLimitExceededException 如果超出最大大小
     * @throws FileNameLengthLimitExceededException 文件名太长
     * @throws FileInvalidExtensionException 文件校验异常
     * @throws IOException 比如读写文件出错时
     */
    public static String upload(String baseDir, MultipartFile file, String[] allowedExtension)
            throws FileSizeLimitExceededException, FileNameLengthLimitExceededException,
            FileInvalidExtensionException, IOException{
        // 文件名称长度
        int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length();
        // 长度是否大于默认长度
        if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) {
            throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
        }

        // 校验文件后缀是否为允许的文件后缀
        assertAllowed(file, allowedExtension);

        // 获取带默认文件夹的文件名
        String fileName = extractFilename(file);

        // 获取文件路径 - 不存在对应路径就创建
        String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
        // 将文件存入指定路径下
        file.transferTo(Paths.get(absPath));
        return getPathFileName(fileName);
    }

    /**
     * 文件大小校验
     *
     * @param file 上传的文件
     * @throws FileSizeLimitExceededException 如果超出最大大小
     * @throws FileInvalidExtensionException 文件校验异常
     */
    public static void assertAllowed(MultipartFile file, String[] allowedExtension)
            throws FileSizeLimitExceededException, FileInvalidExtensionException {
        // 获取文件大小
        long size = file.getSize();
        // 判断是否大于默认大小
        if (size > DEFAULT_MAX_SIZE) {
            throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
        }

        // 获取文件名称
        String fileName = file.getOriginalFilename();
        // 获取文件后缀
        String extension = getExtension(file);
        // 允许的文件后缀不为空, 并且判断文件后缀是否为允许的文件后缀
        if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) {
            // 图片文件后缀
            if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) {
                throw new FileInvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
                        fileName);
            } else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) {
                // Flash 文件后缀
                throw new FileInvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
                        fileName);
            } else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) {
                // 媒体文件后缀
                throw new FileInvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
                        fileName);
            } else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) {
                // 视频文件后缀
                throw new FileInvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension,
                        fileName);
            } else {
                throw new FileInvalidExtensionException(allowedExtension, extension, fileName);
            }
        }
    }

    /**
     * 获取带默认文件夹的文件名，如/yyyy/MM/dd/xxx.jpg
     *
     * @param file 上传文件对象
     * @return  返回文件的绝对路径
     */
    public static String extractFilename(MultipartFile file) {
        return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
                FilenameUtils.getBaseName(file.getOriginalFilename()), SeqUtils.getId(SeqUtils.uploadSeqType), getExtension(file));
    }

    /**
     * 获取文件上传绝对路径
     *
     * @param uploadDir 上传文件路径
     * @param fileName 上传文件名
     * @return 返回文件上传的绝对路径 - 包含文件名
     */
    private static File getAbsoluteFile(String uploadDir, String fileName) {

        // 目的文件对象 - 文件路径 + 文件名
        File desc = new File(uploadDir + File.separator + fileName);

        // 文件是否存在
        if (!desc.exists()) {
            // 父类文件路径是否存在
            if (!desc.getParentFile().exists()) {
                // 不存在先创建父类文件路径
                desc.getParentFile().mkdirs();
            }
        }
        // 返回对应的文件
        return desc.isAbsolute() ? desc : desc.getAbsoluteFile();
    }

    /**
     * 拼接文件名
     *
     * @param fileName 对应的文件名
     * @return 返回拼接好的文件名
     */
    private static String getPathFileName(String fileName) {
        return "/" + fileName;
    }

    /**
     * 获取带自定义文件夹的文件名，如/temp/xxx.jpg
     *
     * @param path 自定义文件路径
     * @param file 上传文件对象
     * @return 返回拼接的文件路径 + 文件名
     */
    public static String extractFilename(String path, MultipartFile file) {
        return StringUtils.format("{}/{}_{}.{}", path,
                FilenameUtils.getBaseName(file.getOriginalFilename()), SeqUtils.getId(SeqUtils.uploadSeqType), getExtension(file));
    }

    /**
     * 拼接随机序列号的文件名
     *
     * @param file 上传文件对象
     * @return 返回拼接的序列文件名
     */
    public static String getRandomFilename(MultipartFile file) {
        return StringUtils.format("{}-{}", SeqUtils.getId(SeqUtils.uploadSeqType), getFullFileName(file));
    }

    /**
     * 获取随机文件夹，年和天生成文件夹
     *
     * @return 获取随机文件路径
     */
    public static String getRandomPath() {
        return DateUtils.datePath();
    }



    /**
     * 判断 MIME 类型是否是允许的 MIME 类型
     *
     * @param extension        上传文件类型
     * @param allowedExtension 允许上传文件类型
     * @return 返回判断结果
     */
    public static boolean isAllowedExtension(String extension, String[] allowedExtension) {
        // 遍历允许的文件后缀数据集
        for (String str : allowedExtension) {
            // 是否匹配
            if (str.equalsIgnoreCase(extension)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取文件名的后缀
     *
     * @param file 上传文件对象
     * @return 返回上传文件后缀
     */
    public static String getExtension(MultipartFile file) {
        // 获取文件后缀
        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
        // 后缀判空
        if (StringUtils.isEmpty(extension)) {
            extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));
        }
        return extension;
    }

    /**
     * 获取文件的文件名，包含后缀的文件名
     *
     * @param file 完整的文件名 - 包含后缀
     * @return  返回完整的文件名
     */
    public static String getFullFileName(MultipartFile file) {
        return FilenameUtils.getName(file.getOriginalFilename());
    }

    /**
     * 给图片加水印并且压缩图片
     *
     * @param sourceFile 文件数据
     * @param fileName 文件名称
     * @param uploadImageConfig 上传图片配置信息
     * @return 返回添加了水印的文件对象
     */
    public static MultipartFile compressPic(MultipartFile sourceFile, String fileName, UploadImageConfig uploadImageConfig) {

        // 上传文件大小判断是否超出允许上传的最大大小
        if (sourceFile.getSize() > Integer.parseInt(uploadImageConfig.getMaxSize()) * 1024L) {
            throw new ServiceException("上传的图片大小超过系统设定值" + uploadImageConfig.getMaxSize() + "KB!");
        }
        // 输出流对象
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try {
            // 获取上传文件二进制数组
            byte[] fileBytes = sourceFile.getBytes();

            // 将其转为 Java 图像处理对象
//            Thumbnails.Builder<InputStream> streamBuilder = (Thumbnails.Builder<InputStream>) Thumbnails.of(new ByteArrayInputStream(fileBytes));
            Thumbnails.Builder<? extends InputStream> streamBuilder = Thumbnails.of(new ByteArrayInputStream(fileBytes));

            // 是否设置了图片缩放比例
            boolean dvSetScale = false;
            // 图片压缩处理
            if (Constants.STR_ZERO.equals(uploadImageConfig.getZipSwitch())) {
                // 上传文件大小是否大于文件压缩最小值
                if (sourceFile.getSize() > Integer.parseInt(uploadImageConfig.getZipMinSize()) * 1024L) {
                    // 是否为 PNG 图片
                    if (getExtension(sourceFile).equalsIgnoreCase(Constants.FILE_EXT_PNG)) {
                        // png 转 jpg 处理
                        ByteArrayOutputStream outputStreamJpg = new ByteArrayOutputStream();
                        // 调用图像处理方法进行转换
                        Thumbnails.of(sourceFile.getInputStream())
                                .scale(1)
                                .outputFormat("jpg")
                                .toOutputStream(outputStreamJpg);
                        streamBuilder = Thumbnails.of(new ByteArrayInputStream(outputStreamJpg.toByteArray()));
                    }
                    // 设置图片缩放比例
                    dvSetScale = true;
                    streamBuilder.scale(new Float(uploadImageConfig.getScale()))
                            .outputQuality(new Float(uploadImageConfig.getQuality()));
                }
            }

            // 图片水印处理
            if (Constants.STR_ZERO.equals(uploadImageConfig.getWaterMarkSwitch())) {
                // 处理水印
                BufferedImage image = selectSize(new ByteArrayInputStream(fileBytes), uploadImageConfig.getWaterMarkContent(), uploadImageConfig.getRgb());
                streamBuilder.watermark(Positions.valueOf(uploadImageConfig.getPositions()), image, new Float(uploadImageConfig.getAlpha()));
            }

            // 未设置图片缩放比例时
            if (!dvSetScale) {
                streamBuilder.scale(1);
            }

            // 将转换的图片文件转为输出流
            streamBuilder.toOutputStream(outputStream);
            return new MockMultipartFile(fileName, fileName, "text/plain", outputStream.toByteArray());
        } catch (Exception e) {
            log.error("图片加水印失败, 异常信息: {}", e.getMessage(), e);
            throw new ServiceException("图片加水印失败，请联系系统管理员！");
        }
    }

    /**
     * 水印图片处理
     *
     * @param inputStream      文件流
     * @param waterMarkContent 水印内容
     * @param rgb              颜色rgb值
     * @return 返回处理水印后的图片对象
     */
    public static BufferedImage selectSize(InputStream inputStream, String waterMarkContent, String rgb) throws Exception {
        // 转为图片对象
        BufferedImage bufferedImage = ImageIO.read(inputStream);
        // 去掉颜色字符中的 #
        rgb = rgb.replaceAll("#", "");
        // 对象判空
        if (bufferedImage == null) {
            throw new ServiceException("读取图片出错，请确认图片格式是否正确！");
        }
        // 宽度
        int width = bufferedImage.getWidth();
        BufferedImage image;
        // 宽度大小判断
        if (width > BIGFILE) {
            maxWidth = (int) ((width / 3) > FONTMAX.getSize() * 17 ? (width / (0.6f)) : FONTMAX.getSize() * 17);
            image = getImage(waterMarkContent, new Color(Integer.parseInt(rgb, 16)), FONTMAX);
        } else if (width > MEDFILE) {
            maxWidth = (int) ((width / 2) > FONTBIG.getSize() * 17 ? (width / (0.6f)) : FONTBIG.getSize() * 17);
            image = getImage(waterMarkContent, new Color(Integer.parseInt(rgb, 16)), FONTBIG);
        } else if (width > SMALLFILE) {
            maxWidth = (int) ((width / (1.5f)) > FONTMEDIUM.getSize() * 17 ? (width / (0.6f)) : FONTMEDIUM.getSize() * 17);
            image = getImage(waterMarkContent, new Color(Integer.parseInt(rgb, 16)), FONTMEDIUM);
        } else {
            maxWidth = (int) ((width / (1.2f)) > FONTSMALL.getSize() * 17 ? (width / (0.6f)) : FONTSMALL.getSize() * 17);
            image = getImage(waterMarkContent, new Color(Integer.parseInt(rgb, 16)), FONTSMALL);
        }
        return image;
    }

    /**
     * 创建新图片
     *
     * @param waterMarkContent 水印文字内容
     * @param color            水印字体颜色
     * @param font             水印文字字体
     * @return 返回创建的新图片
     */
    public static BufferedImage getImage(String waterMarkContent, Color color, Font font) {
        // 获取高度与宽度
        Map<String, Integer> heightWith = getHeightWidth(waterMarkContent, font);
        int height = heightWith.get("height");
        int width = heightWith.get("width");
        //获取到长度后绘制正确的图片
        BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2 = (Graphics2D) bi.getGraphics();
        g2.setBackground(Color.WHITE);
        g2.clearRect(0, 0, width, height);
        return addWatermark(bi, waterMarkContent, color, font);
    }

    /**
     * 计算画板尺寸
     *
     * @param waterMarkContent 水印文本内容
     * @param font             水印文本字体
     * @return 返回画板大小对象
     */
    public static Map<String, Integer> getHeightWidth(String waterMarkContent, Font font) {
        // 字体大小
        int fontSize = font.getSize();
        // 这个image是个临时的 为了获取绘制的文字的长度
        BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = image.createGraphics();
        // 创建一个FontMetrics对象
        FontMetrics fm = g.getFontMetrics(font);
        //获取文字的长度 除以maxWidth 就是水印的行数
        int height = 0;
        String[] strings = waterMarkContent.split("\\\\");
        for (String string : strings) {
            int i = fm.stringWidth(string) / maxWidth + 1;
            height += i;
        }
        //乘以行高 就是实际的高度
        height = height * (fontSize + fontSize / 4);
        int width = Math.min(maxWidth, fm.stringWidth(waterMarkContent));
        Map<String, Integer> map = new HashMap<>(2);
        map.put("width", width);
        map.put("height", height);
        return map;
    }

    /**
     * 生成水印图片
     *
     * @param sourceImg        源图片
     * @param waterMarkContent 水印文字内容
     * @param color            水印文字颜色
     * @param font             水印文字字体
     * @return 返回添加了水印的图片
     */
    public static BufferedImage addWatermark(BufferedImage sourceImg, String waterMarkContent, Color color, Font font) {
        // 字体大小
        int fontSize = font.getSize();
        // 设置水印文字的旋转角度
        // Integer degree = null;
        try {
            // 获取图片的宽
            int srcImgWidth = sourceImg.getWidth(null);
            // 获取图片的高
            int srcImgHeight = sourceImg.getHeight(null);
            // 加水印
            BufferedImage bufImg = new BufferedImage(srcImgWidth, srcImgHeight, BufferedImage.TYPE_INT_RGB);
            // 得到画笔
            Graphics2D g = bufImg.createGraphics();
            g.drawImage(sourceImg, 0, 0, srcImgWidth, srcImgHeight, null);
            // 设置水印颜色
            g.setColor(color);
            // 设置字体
            //自动根据水印大小调节字体大小
            g.setFont(font);
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1f));
            // 设置水印文字透明度
            /*if (null != degree) {
                // 设置水印旋转
                g.rotate(Math.toRadians(degree));
            }*/
            JLabel jLabel = new JLabel(waterMarkContent);
            jLabel.setFont(font);
            FontMetrics fontMetrics = jLabel.getFontMetrics(font);
            int textLength = waterMarkContent.length();
            //一行的总长度，用于判断是否超出了范围
            int totalWidth = fontMetrics.stringWidth(waterMarkContent);
            //计算一行的高度
            int textHeight = fontMetrics.getHeight();
            // 总长度超过了整个长度限制
            if (totalWidth > maxWidth) {
                //已经写了多少行
                int alreadyWriteLine = 0;
                //目前一行写的长度
                int nowWidth = 0;
                for (int i = 0; i < textLength; i++) {
                    //获取单个字符的长度
                    int oneWordWidth = fontMetrics.charWidth(waterMarkContent.charAt(i));
                    //判断目前的一行加上这个字符的长度是否超出了总长度
                    int tempWidth = oneWordWidth + nowWidth;
                    if (tempWidth > maxWidth) {
                        // 如果超出了一行的总长度，则要换成下一行
                        nowWidth = 0;
                        alreadyWriteLine++;
                        int writeY = fontSize - 5 + alreadyWriteLine * (textHeight);
                        g.drawString(waterMarkContent.charAt(i) + "", nowWidth, writeY);
                        nowWidth = oneWordWidth;
                    } else {
                        if (waterMarkContent.charAt(i) == '\\') {
                            //如果是换行符，则不用写入，直接跳过
                            nowWidth = maxWidth + 1;
                            continue;
                        }
                        // 当前行长度足够，可以直接画
                        int writeY = fontSize - 5 + alreadyWriteLine * (textHeight);
                        g.drawString(waterMarkContent.charAt(i) + "", nowWidth, writeY);
                        nowWidth = tempWidth;
                    }
                }
            } else {
                //没有超过限制，直接画
                g.drawString(waterMarkContent, 0, fontSize - fontSize / 10);
            }
            // 释放资源
            g.dispose();
            return transferAlpha(bufImg);
        } catch (Exception e) {
            log.error("生成水印图片失败, 异常信息: {}", e.getMessage(), e);
        }
        return null;
    }

    /**
     * 转化成透明背景的图片
     *
     * @param bufImg 源图片
     * @return 返回转换完成的图片对象
     */
    public static BufferedImage transferAlpha(BufferedImage bufImg) {
        try {
            ImageIcon imageIcon = new ImageIcon(bufImg);
            BufferedImage bufferedImage = new BufferedImage(imageIcon.getIconWidth(), imageIcon.getIconHeight(),
                    BufferedImage.TYPE_4BYTE_ABGR);
            Graphics2D g2D = (Graphics2D) bufferedImage.getGraphics();
            g2D.drawImage(imageIcon.getImage(), 0, 0, imageIcon.getImageObserver());
            int alpha = 0;
            for (int j1 = bufferedImage.getMinY(); j1 < bufferedImage.getHeight(); j1++) {
                for (int j2 = bufferedImage.getMinX(); j2 < bufferedImage.getWidth(); j2++) {
                    int rgb = bufferedImage.getRGB(j2, j1);
                    int r = (rgb & 0xff0000) >> 16;
                    int g = (rgb & 0xff00) >> 8;
                    int b = (rgb & 0xff);
                    if (((255 - r) < 30) && ((255 - g) < 30) && ((255 - b) < 30)) {
                        rgb = ((alpha + 1) << 24) | (rgb & 0x00ffffff);
                    }
                    bufferedImage.setRGB(j2, j1, rgb);
                }
            }
            g2D.drawImage(bufferedImage, 0, 0, imageIcon.getImageObserver());
            return bufferedImage;
        } catch (Exception e) {
            log.error("将图片转化成透明背景的图片失败, 异常信息: {}", e.getMessage(), e);
        }
        return null;
    }

}
